<!--
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->


<!--
@group Polymer Core Elements

@element core-layout-grid
@status beta
@homepage github.io

TODO
-->
<link rel="import" href="../polymer/polymer.html">

<polymer-element name="core-layout-grid" attributes="nodes layout auto">
<script>
  (function() {

    Polymer('core-layout-grid', {

      nodes: null,
      layout: null,
      auto: false,

      created: function() {
        this.layout = [];
      },

      nodesChanged: function() {
        this.invalidate();
      },

      layoutChanged: function() {
        this.invalidate();
      },

      autoNodes: function() {
        this.nodes = this.parentNode.children.array().filter(
          function(node) {
            switch(node.localName) {
              case 'core-layout-grid':
              case 'style':
                return false;
            }
            return true;
          }
        );
      },

      invalidate: function() {
        if (this.layout && this.layout.length) {
          // job debounces layout, only letting it occur every N ms
          this.layoutJob = this.job(this.layoutJob, this.relayout);
        }
      },

      relayout: function() {
        if (!this.nodes || this.auto) {
          this.autoNodes();
        }
        layout(this.layout, this.nodes);
        this.asyncFire('core-layout');
      }

    });

    //

    var lineParent;

    function line(axis, p, d) {
      var l = document.createElement('line');
      var extent = (axis === 'left' ? 'width' : 
        (axis === 'top' ? 'height' : axis));
      l.setAttribute('extent', extent);
      if (d < 0) {
        axis = (axis === 'left' ? 'right' : 
          (axis === 'top' ? 'bottom' : axis));
      }
      p = Math.abs(p);
      l.style[axis] = p + 'px';
      l.style[extent] = '0px';
      lineParent.appendChild(l);
    }

    var colCount, colOwners, rowCount, rowOwners;

    function matrixillate(matrix) {
      // mesaure the matrix, must be rectangular
      rowCount = matrix.length;
      colCount = rowCount && matrix[0].length || 0;
      // transpose matrix
      var transpose = [];
      for (var i=0; i<colCount; i++) {
        var c = [];
        for (var j=0; j<rowCount; j++) {
          c.push(matrix[j][i]);
        }
        transpose.push(c);
      }
      // assign sizing control
      colOwners = findOwners(matrix);
      rowOwners = findOwners(transpose);
      //console.log('colOwners', colOwners);
      //console.log('rowOwners', rowOwners);
    }

    function findOwners(matrix) {
      var majCount = matrix.length;
      var minCount = majCount && matrix[0].length || 0;
      var owners = [];
      // for each column (e.g.)
      for (var i=0; i<minCount; i++) {
        // array of contained areas
        var contained = {};
        // look at each row to find a containing area
        for (var j=0; j<majCount; j++) {
          // get the row vector
          var vector = matrix[j]
          // node index at [i,j]
          var nodei = vector[i];
          // if a node is there
          if (nodei) {
            // determine if it bounds this column
            var owns = false;
            if (i === 0) {
              owns = (i === minCount-1) || (nodei !== vector[i+1]);
            } else if (i === minCount - 1) {
              owns = (i === 0) || (nodei !== vector[i-1]);
            } else {
              owns = nodei !== vector[i-1] && nodei !== vector[i+1];
            }
            if (owns) {
              contained[nodei] = 1;
            }
          }
          // store the owners for this column
          owners[i] = contained;
        }
      }
      return owners;
    }

    var nodes;

    function colWidth(i) {
      for (var col in colOwners[i]) {
        col = Number(col);
        if (col === 0) {
          return 96;
        }
        var node = nodes[col - 1];
        if (node.hasAttribute('h-flex') || node.hasAttribute('flex')) {
          return -1;
        }
        var w = node.offsetWidth;
        //console.log('colWidth(' + i + ') ==', w);
        return w;
      }
      return -1;
    }

    function rowHeight(i) {
      for (var row in rowOwners[i]) {
        row = Number(row);
        if (row === 0) {
          return 96;
        }
        var node = nodes[row - 1];
        if (node.hasAttribute('v-flex') || node.hasAttribute('flex')) {
          return -1;
        }
        var h = node.offsetHeight;
        //console.log('rowHeight(' + i + ') ==', h);
        return h;
      }
      return -1;
    }

    var m = 0;

    function railize(count, sizeFn) {
      //
      // create rails for `count` tracks using 
      // sizing function `sizeFn(trackNo)`
      //
      // for n tracks there are (n+1) rails
      //
      //   |track|track|track|
      //  0|->sz0|->sz1|<-sz2|0
      //
      //   |track|track|track|
      //  0|->sz0|     |<-sz2|0
      //
      // there can be one elastic track per set
      //
      //   |track|track|track|track|
      //  0|-->s0|-->s1|<--s1|<--s2|0
      //
      // sz1 spans multiple  tracks which makes
      // it elastic (it's underconstrained)
      //
      var rails = [];
      var a = 0;
      for (var i=0, x; i<count; i++) {
        rails[i] = {p: a, s: 1};
        x = sizeFn(i) + m + m;
        if (x == -1) {
          break;
        }
        a += x;
      }
      if (i === count) {
        rails[i] = {p: 0, s: -1};
      }
      var b = 0;
      for (var ii=count, x; ii>i; ii--) {
        rails[ii] = {p: b, s: -1};
        x = sizeFn(ii - 1) + m + m;
        if (x !== -1) {
          b += x;
        }
      }
      return rails;
    }

    // TODO(sjmiles): this code tries to preserve actual position,
    // so 'unposition' is really 'naturalize' or something
    function unposition(box) {
      var style = box.style;
      //style.right = style.bottom = style.width = style.height = '';
      style.position = 'absolute';
      style.display = 'inline-block';
      style.boxSizing = style.mozBoxSizing = 'border-box';
    }

    function _position(style, maj, min, ext, a, b) {
      style[maj] = style[min] = '';
      style[ext] = 'auto';
      if (a.s < 0 && b.s < 0) {
        var siz = a.p - b.p - m - m;
        style[ext] = siz + 'px';
        var c = 'calc(100% - ' + (b.p + siz + m) + 'px' + ')';
        style[maj] = '-webkit-' + c;
        style[maj] = c;
      } else if (b.s < 0) {
        style[maj] = a.p + m + 'px';
        style[min] = b.p + m + 'px';
      } else {
        style[maj] = a.p + m + 'px';
        style[ext] = b.p - a.p - m - m + 'px';
      }
    }

    function position(elt, left, right, top, bottom) {
      _position(elt.style, 'top', 'bottom', 'height', rows[top], 
          rows[bottom]);
      _position(elt.style, 'left', 'right', 'width', columns[left], 
          columns[right]);
    }

    function layout(matrix, anodes, alineParent) {
      //console.group('layout');

      lineParent = alineParent;
      nodes = anodes;
      matrixillate(matrix);

      nodes.forEach(unposition);

      columns = railize(colCount, colWidth);
      rows = railize(rowCount, rowHeight);

      if (alineParent) {
        //console.group('column rails');
        columns.forEach(function(c) {
          //console.log(c.p, c.s);
          line('left', c.p, c.s);
        });
        //console.groupEnd();

        //console.group('row rails');
        rows.forEach(function(r) {
          //console.log(r.p, r.s);
          line('top', r.p, r.s);
        });
        //console.groupEnd();
      }

      //console.group('rail boundaries');
      nodes.forEach(function(node, i) {
        // node indices are 1-based
        var n = i + 1;
        // boundary rails
        var l, r, t = 1e10, b = -1e10;
        matrix.forEach(function(vector, i) {
          var f = vector.indexOf(n);
          if (f > -1) {
            l = f;
            r = vector.lastIndexOf(n) + 1;
            t = Math.min(t, i);
            b = Math.max(b, i) + 1;
          }
        });
        if (l == undefined) {
          //console.log('unused');
          node.style.position = 'absolute';
          var offscreen = node.getAttribute('offscreen');
          switch (offscreen) {
            case 'basement':
              node.style.zIndex = 0;
              break;
            case 'left':
            case 'top':
              node.style[offscreen] = node.offsetWidth * -2 + 'px';
              break;
            case 'right':
              node.style.left = node.offsetParent.offsetWidth 
                  + node.offsetWidth + 'px';
              break;
            case 'bottom':
              node.style.top = node.parentNode.offsetHeight 
                  + node.offsetHeight + 'px';
              break;
            default:
              node.style[Math.random() >= 0.5 ? 'left' : 'top'] = '-110%';
          }
          //node.style.opacity = 0;
          node.style.pointerEvents = 'none';
        } else {
          node.style.pointerEvents = '';
          //node.style.opacity = '';
          //console.log(l, r, t, b);
          position(node, l, r, t, b);
        }
      });
      //console.groupEnd();
      //console.groupEnd();
    }

  })();
</script>
</polymer-element>